-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Websock support #5063
Websock support #5063
Conversation
67ab9dc
to
e6818e3
Compare
@josevalim this PR now stands complete for review |
lib/phoenix/socket/transport.ex
Outdated
Of note, this behaviour is a superset of the `WebSock` behaviour, and | ||
as such all implementations of `Phoenix.Socket.Transport` are also | ||
implementations of `WebSock`. It is not possible to note this in code | ||
without raising warnings about conflicting implementations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah... I see now. Phoenix.Socket.Transport in Phoenix is generic. WebSock is an implementation of Phoenix.Socket.Transport for WebSockets + a generic abstraction for adapters. So now that I think about it, I don't think we should list WebSock
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I.e. we should just remove this whole paragraph and instead say that WebSock implements Phoenix.Socket.Transport.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I.e. we should just remove this whole paragraph and instead say that WebSock implements Phoenix.Socket.Transport.
Do you mean that we should say that here, just in a differently worded comment, or that WebSock
should actually @impl
Phoenix.Socket.Transport over in the WebSock project?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW I think the Phoenix.Socket.Transport API itself is 100% fine as a WebSocket abstraction (as we've talked about at length on previous PRs). This is more a question of 'which behaviour subsumes which behaviour' and being able to express that at the limits of the language's behaviour mechanism.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, the thing is that Phoenix.Socket.Transport is meant to be transport agnostic, so it doesn't make sense to say it implements WebSock.
At the same time, we don't want WebSock to depend on Phoenix, because people can use it without Phoenix.
So I think we should just informally says in WebSocket that it implements Phoenix.Socket.Transport and that's it. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is also the reason why I think it should exist in the Phoenix org (I was originally considering it should be Plug but I think it is more aligned to Phoenix).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Happy to note it at both ends (in fact, I'm pretty sure I already do note the similarity in the WebSock docs, at least as a source of inspiration). My main worry (speaking from experience as I pieced together an understanding of all of Phoenix's internals) is that without some sort of note here, it's not at all obvious how a fundamental part of Phoenix's WebSocket implementation works. Maybe the note could instead live in Phoenix.Transport.WebSocket, as a code comment near the WebSock.upgrade/4
call?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually think that's better, TBH. As I've mentioned before, someone trying to understand how WebSockets work in Phoenix is going to check out Phoenix.Transports.WebSocket first (just given the name), and so as many breadcrumbs as possible should be left there. It also keeps Phoenix.Socket.Transport agnostic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As an aside, the ability for behaviours to compose other behaviours is something I've wished for on multiple occasions (a concrete example is ThousandIsland.Handler wanting to be a superset of GenServer). Has this been proposed before / are there plans for it / is it something you'd entertain a PR for in the language?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe the note could instead live in Phoenix.Transport.WebSocket, as a code comment near the WebSock.upgrade/4 call?
Agreed!
Has this been proposed before / are there plans for it / is it something you'd entertain a PR for in the language?
Nothing we can do. Behaviours are somewhat tied to Dialyzer and Dialyzer will complain anyway. :(
Yeah, this is as good as it gets. :) I will talk to @chrismccord about his feelings on this and the ownership. Would you be ok with moving WebSock to the Phoenix org? You'd still remain as admin but we would be around to help/backup. |
I have no problem with that! I've actually been thinking about the longer-term plans for the entire Thousand Island / Bandit / WebSock stack and how there should be an In any case, having been bit firsthand by 'the bus effect' of maintainers just disappearing I'm more than happy to have stuff live in an org. |
e6818e3
to
2770c1b
Compare
Docs updated, merge conflicts resolved. I did a top level comment in Phoenix.Transport.WebSocket that outlines the overall lifecycle; it's the comment that I wish existed when I started exploring Phoenix, and in the first place anyone would look :). Let's wait to hear @chrismccord 's take on the matter, but I'm happy to move the WebSock project to any org you'd like |
2770c1b
to
3444924
Compare
I'm running into an issue with Hex dependencies now that WebSock depends on Cowboy. I'd originally conceived of WebSock as being strictly behaviour, but now that it's an actual abstraction on top of Cowboy & Bandit there's an inverted dependency that makes it so Bandit ends up having a transitive dependency on Cowboy (not great). It also makes WebSock's handling of Bandit upgrades untestable since there's a circular dependency (this isn't a big deal since the upgrade is a one-line passthrough to Bandit's native WebSock support). To fix this I need Bandit to be able to depend on just the WebSock behaviour and not pull in any of the adapter dependencies. Similar to the split between plug and plug_cowboy, I think I'm going to end up cutting a Let's hold off on merging this until I get that lined up. Should be later today (EDT). |
@mtrudel why does Bandit depend on WebSock? Doesn't Bandit expose a more general WebSockets API anyway? |
No - the whole intent of WebSock was to be that general WebSocket API (and in my mind, it still mostly is). If you look at WebSock's 'adapter' to Bandit, it just wires Bandit directly into the handler (see https://github.com/mtrudel/websock/blob/main/lib/websock.ex#L167-L168). Running on Bandit you'll never see any WebSockAdapter frames in a stack trace, because there's no code there at all. The discussion we had previously that ended up reshaping the WebSock API in the mold of Transport was more about the tradeoffs of seeing the API from the perspective of a server implementor (what it was originally) or the perspective of a framework implementor (what it is now). I still think it stands as a generalized WebSocket API, just one that makes more sense from the framework side of things now. Splitting just the behaviour out into its own library allows it to continue being a generalized API. The implementations on top of Bandit or Cowboy stand apart from that. Before I get too far into this, do you have a better idea? I'm all ears for a different approach here. |
What if:
? |
That's more or less the split that I have in mind, just that the base behaviour doesn't try to be generic (it's more specific than a generalized socket interface, since it knows about frame types. It doesn't really make sense to not be WebSocket specific). The split is actually turning out to be pretty straightforward, and enables everything I was hoping to in terms of declaring conformance and testability. Should be up shortly. |
Updated (see d7730a2 for the relevant change to this PR; it's trivial). You can see WebSock/WebSockAdapter split at: (as before, happy for both project to move under the Phoenix (or any other) umbrella). Tests are green up and down the whole stack; everything is once again ready for review! |
336de8d
to
d7730a2
Compare
Hi @mtrudel! One question: is there a particular reason why you preferred to not go with |
2eaea1e
to
af00565
Compare
Yep - the I've always conceived the WebSock (née Sock) API to be a WebSocket abstraction and nothing more (specifically, I've never thought of it as a generalization over other transports such as long polling). This is in contrast to Transport, which intends to be generic over all transports at the expense of fidelity. The WebSock spec is pretty specific to WebSockets (insofar as it knows about frame types and the specifics of the overall lifecycle). The tl;dr here is that I think this difference of opinion (if I can even call it that) comes from the differing perspectives of a server-first or framework-first frame of reference. From my server-first perspective, I set out for WebSock to be the solution to "how can I generalize different servers' WebSocket implementation?" whereas from a framework-first perspective it may have been seen as trying to solve "how can I generalize 'sockets' across different transports?". WebSock is not and never has been trying to be the latter of those two questions [1], and to the extent that it manages to fill that role today it's partly 'happy accident' and partly from having conceded on some of the higher fidelity aspects of WebSockets in the API (connection close codes are a good example of this). All in all I'm very happy with how it turned out! You can see some of this change in perspective by comparing what's here now to the shape of my early PRs on this work, where I was approaching the abstraction very much from the perspective of 'Plug for WebSockets' rather than trying to be anything more general. From a 'server-first' perspective I still think that model is/was the right approach, but shipping (and building consensus!) is more important than purity! An API that isn't adopted isn't worth anything. The split between the WebSock behaviour and the WebSockAdapter adapter lib reflected here is purely a matter of necessity, to allow code both 'above' and 'below' the adapter layer to be able to interact with the spec (you'll note that Bandit depends on A bit longwinded, but I hope this helps explain the thinking here? [1] Having a library that generalized over multiple transports could be a really interesting thing to evolve towards in the future! |
Updated to WebSock 0.4.3 which provides support for return values to specify multiple frames to send:
This is a strictly additive change to the existing contract |
Thanks for all the hard work @mtrudel I just tried out this PR on master and can confirm that cowboy and bandit are both working with a fresh liveview project, it was seamless! I've reviewed this PR as well as |
@mtrudel, can you please add me as admin to both projects so I transfer them to the Phoenix org and merge this? |
Done! You should have two invites pending. Not sure if you'll be able to transfer them since they're in a personal account and not an org. Let's see. |
I'll have to add you as an admin to the hex projects too. Will do that later this morning! |
@josevalim I added you to the hex packages. In terms of the repos, it looks like I need to be the one to do the transfer, and I can only do so to another personal account or to an org where I have permission to create repos. Probably easiest if I just transfer it to your |
@josevalim done. You should now have two invitations to transfer the repos to your personal account, from where you can immediately transfer them to phoenixframework. Note that you'll probably need to add me in the org, as I don't think my permissions will transfer all the way through. |
lib/phoenix/socket/transport.ex
Outdated
|
||
means `child_spec([shutdown: 5000])` will be invoked. | ||
""" | ||
@callback child_spec(keyword) :: :supervisor.child_spec() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was this moved to Phoenix.Socket? I don't think it is actually part of Phoenix.Socket, because the user does not need to define a child_spec
for Phoenix.Socket
. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Note that we've talked about this before, but I suppose never came to a conclusion on it)
The actual concrete implementation of this callback hasn't changed - it's still use
d into the Socket at https://github.com/mtrudel/phoenix/blob/af00565585bdbc5644f9ed0a1077d52d87ac3f0f/lib/phoenix/socket.ex#L309-L311. The user does not and never has needed to define a child_spec
themselves.
The only thing that changed here is which behaviour this is part of. The scope of this callback based on its docs makes this seem like a better home for it. Not strictly blocking, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be part of Phoenix.Socket.Transport
because someone can implement Phoenix.Socket.Transport
so they bypass the whole channel machinery and I believe that, without this callback, it won't work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One comment and I think we can ship this.
af00565
to
e070f26
Compare
💚 💙 💜 💛 ❤️ |
A M A Z I N G Now that this is finalized I've published the last of the Bandit 0.5 series ( I've got a number of follow-on PRs relating to completing the transition to Cowboy being 'one among many' possible servers for Phoenix, but they can & should wait until after 1.7 is out and Bandit's integration has had some time to bake in. They're all doc & generator related, nothing core. |
Awesome work @mtrudel! We will reap the benefits of these changes for a long time! |
This is part two of #5030, adding in support for (newly renamed) WebSock facilitated upgrades. This makes Phoenix entirely agnostic to the underlying server, so long as it provides Plug & WebSock support.
The WebSock behaviour as published in 0.4.1 mirrors
Phoenix.Socket.Transport
nearly exactly, with a few additive changes:c:WebSock.init/1
can now return the same set of tuples asc:WebSock.handle_in/2
et al (this set include the current{:ok, state}
tuple returned today). This allowsinit
to send messages at connection timec:WebSock.terminate/1
defines more possible arguments, codifying a number of common termination reasons such as server shutdown or timeout. The list is described in the WebSock documentation. Given thatc:Phoenix.Socket.Transport.terminate/1
has an open-ended spec currently this should be fine{:push, ...}
or{:reply, ...}
may indicate the transmission of any type of framec:Phoenix.Socket.Transport.start_link/1
moves toc:Phoenix.Socket.start_link/1
and is not part of theWebSock
specification